<?php
/**
 * Database
 *
 * @category	DataSource
 * @package		dayscript.datasource.sql
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-26 $
 */

namespace dayscript\datasource\sql;

/**
 * Database
 *
 * @category	DataSource
 * @package		dayscript.datasource.sql
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\dayscript\datasource\DataSource, \dayscript\log\LogManager, \dayscript\Map, \dayscript\exception\Exception
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-26 $
 */
abstract class Database implements \dayscript\datasource\DataSource	{
	/**
	 * Datasource Connection identifier
	 * @var string $name
	 */
	protected $name = null;
	/**
	 * Database Configuration options
	 * @var \dayscript\Map $config
	 */
	protected $config = null;
	/**
	 * Database statement
	 * @var \dayscript\datasource\sql\Statement $statement
	 */
	protected $statement = null;
	/**
	 * Database statement
	 * @var \dayscript\datasource\sql\Statement $statement
	 */
	protected $fetchMode = self::FETCH_ASSOC;
	/**
	 * Database symbol for quoting identifiers
	 * @var char $quoteIdentifierSymbol
	 */
	protected $quoteIdentifierSymbol = '"';
	/**
	 * Database symbol for quoting values
	 * @var char $quoteValueSymbol
	 */
	protected $quoteValueSymbol = '\'';
	/**
	 * Whether or not this Datasource is connected.
	 * @var boolean $connected
	 */
	protected $connected = false;
	/**
	 * Whether or not this Datasource is cacheable.
	 * @var boolean $cacheable
	 */
	protected $cacheable = false;
	/**
	 * Database connection
	 * @var object|resource|null
	 */
	protected $connection = null;
	/**
	 * Specifies whether the database object automatically quotes identifiers.
	 * @var boolean $autoQuoteIdentifiers
	 */
	protected $autoQuoteIdentifiers = true;
	/**
	 * Constant numeric types for quote a general functions
	 */
	const TYPE_INT		= 0;
	const TYPE_BIGINT	= 1;
	const TYPE_FLOAT	= 2;
	/**
	 * Associative array of  numeric datatypes.
	 * @var array $numericDataTypes
	 */
	protected $numericDataTypes = array(
		self::TYPE_INT		=> self::TYPE_INT,
		self::TYPE_BIGINT	=> self::TYPE_BIGINT,
		self::TYPE_FLOAT	=> self::TYPE_FLOAT
	);
	/**
	 * PDO constant values.
	 */
	const FETCH_ASSOC = 2;
    const FETCH_BOTH = 4;
    const FETCH_BOUND = 6;
    const FETCH_CLASS = 8;
    const FETCH_CLASSTYPE = 262144;
    const FETCH_COLUMN = 7;
    const FETCH_FUNC = 10;
    const FETCH_GROUP = 65536;
    const FETCH_INTO = 9;
    const FETCH_LAZY = 1;
    const FETCH_NAMED = 11;
    const FETCH_NUM = 3;
    const FETCH_OBJ = 5;
    const FETCH_ORI_ABS = 4;
    const FETCH_ORI_FIRST = 2;
    const FETCH_ORI_LAST = 3;
    const FETCH_ORI_NEXT = 0;
    const FETCH_ORI_PRIOR = 1;
    const FETCH_ORI_REL = 5;
    const FETCH_SERIALIZE = 524288;
    const FETCH_UNIQUE = 196608;
	
	/**
	 * Database constructor
	 * @return Database
	 */
	public function __construct( )	{
		
	}
	
	/*
	 * Getters / Setters
	 */
	/**
	 * @see \dayscript\datasource\DataSource
	 */
	public function getName( )	{
		return $this->name;
	}
	/**
	 * @see \dayscript\datasource\DataSource
	 */
	public final function getConfig( )	{
		return ( $this->config ? $this->config->toArray( ) : null );
	}
	/**
	 * Database fetch mode
	 * @return int $fetchMode
	 */
	public final function getFetchMode( )	{
		return $this->fetchMode;
	}
	/*
	 * Connection
	 */
	/**
	 * @see \dayscript\datasource\DataSource
	 */
	public final function openConnection( )	{
		if( !$this->connected )	{
			if( $this->config )
				$this->connected = $this->connect( );
			else
				throw new \dayscript\exception\Exception( 'Trying to open a non initialized database connection "' . __CLASS__ . '".' );
		}
	}
	/**
	 * Open a database connection
	 * @return boolean $isConnected
	 */
	protected abstract function connect( );
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public final function isConnected( )	{
		return $this->connected;
	}
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public final function closeConnection( )	{
		if( $this->connected )
			$this->connected = !$this->disconect( );
		return $this->connected;
	}
	/**
	 * Close a database connection
	 * @return boolean $isClosed
	 */
	protected abstract function disconnect( );
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public final function getConnection( )	{
		$this->openConnection( );
		return $this->connection;
	}
	
	/*
	 * Cache
	 */
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public function setCacheDir( $path )	{
		
	}
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public function setCacheEnable( $enabled )	{
		$this->cacheable = ( $enabled && $this->initCache( ) );
	}
	/**
	 * @see \dayscript\datasource\DataSource
	 */
	public function isCacheEnable( )	{
		return $this->cacheable;
	}
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public function deleteCache( )	{
		
	}
	
	/*
	 * Logging
	 */
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public final function canLog( )	{
		return \dayscript\log\LogManager::canLog( \dayscript\log\LogManager::LOG_DATASOURCE );
	}
	/**
	 * @see \dayscript\datasource\DataSource
	 * TODO
	 */
	public final function log( $message, $logType = \dayscript\log\LogManager::LOG_DATASOURCE )	{
		if( \dayscript\log\LogManager::canLog( $logType ) )	{
			$message = \preg_replace( '/[^|\n][\W|-]+[\n|$]/i', '', \strip_tags( $message ) );
			\dayscript\log\LogManager::log( \preg_replace( '/^\\\\?([\\w]*\\\\){2}/', '', \get_class( $this ) ) . ': ' . $message, $logType );
		}
	}
	
	/*
	 * Transactions
	 */
	/**
	 * Leave autocommit mode and begin a transaction.
	 * @return boolean $started Whether the transaction starts
	 */
	public function beginTransaction( )	{
		$this->openConnection( );
		$this->log( 'Transaction: START.' );
		return $this->transactionBegin( );
	}
	/**
	 * Leave autocommit mode and begin a transaction.
	 * @return boolean $started Whether the transaction starts
	 */
	abstract protected function transactionBegin( );
	/**
	 * Commit a transaction and return to autocommit mode.
	 * @return boolean $commited Whether the transaction was commited
	 */
	public function commit( )	{
		$this->openConnection( );
		$this->log( "Commit transaction." );
		return $this->transactionCommit( );
	}
	/**
	 * Commit a transaction and return to autocommit mode.
	 * @return boolean $commited Whether the transaction was commited
	 */
	abstract protected function transactionCommit( );
	/**
     * Roll back a transaction and return to autocommit mode.
	 * @return boolean $rolledBack Whether the transaction rolled back
	 */
	public function rollBack( )	{
		$this->openConnection( );
		$this->log( "RollBack transaction." );
		return $this->transactionRollBack( );
	}
	/**
     * Roll back a transaction and return to autocommit mode.
	 * @return boolean $rolledBack Whether the transaction rolled back
	 */
	abstract protected function transactionRollBack( );

	/*
	 * Quote
	 */
	/**
	 * Sets whether the adapter will automatically quotes identifiers.
	 */
	public function setAutoQuoteIdentifiers( $quote )	{
		$this->autoQuoteIdentifiers = $quote;
	}
	/**
	 * Returns whether the adapter will automatically quotes identifiers.
	 */
	public function isAutoQuoteIdentifiers( )	{
		return $this->autoQuoteIdentifiers;
	}
	/**
	 * Quotes a value for an SQL statement.
	 * If an array is passed as the value, the array values are quoted and returned as a comma-separated string.
	 *
	 * @param mixed $value The value to quote.
	 * @param mixed $type OPTIONAL the SQL datatype name, or constant, or null.
	 * @return mixed A quoted value (or string of separated values).
	 */
	public final function quote( $value, $type = null )	{
		$this->openConnection( );
		if ( \is_array( $value ) )	{
			foreach ( $value as $key => $val )
				$value[$key] = $this->quote( $val, $type );
			return \implode( ', ', $value );
		}

		if ( $type !== null && isset( $this->numericDataTypes[\strtoupper( $type )] ) )	{
			$type = $this->numericDataTypes[\strtoupper( $type )];
			$quoted = '0';
			switch ( $type )	{
				case Database::TYPE_INT:	// 32-bit integer
					$quoted = (string)\intval( $value );
					break;
				case Database::TYPE_BIGINT:	// 64-bit integer
					// ANSI SQL-style hex literals (e.g. x'[\dA-F]+')
					// are not supported here, because these are string
					// literals, not numeric literals.
					if ( \preg_match('/^(
                          [+-]?                  # optional sign
                          (?:
                            0[Xx][\da-fA-F]+     # ODBC-style hexadecimal
                            |\d+                 # decimal or octal, or MySQL ZEROFILL decimal
                            (?:[eE][+-]?\d+)?    # optional exponent on decimals or octals
                          )
                        )/x',
						(string) $value, $matches ) )	{
						
						$quoted = $matches[1];
					}
					break;
				case Database::TYPE_FLOAT:	// float or decimal
					$quoted = \sprintf( '%F', $value );
			}
			return $quoted;
		}

		return $this->quoteValue( $value );
	}
	/**
	 * Quote a raw string.
	 * @param string $value Raw string
	 * @return string Quoted string
	 */
	protected function quoteValue( $value )	{
		if ( \is_int( $value ) )
			return $value;
		else if ( \is_float( $value ) )
			return \sprintf( '%F', $value );
		else if ( \is_null( $value ) )
			return 'NULL';
			
		return $this->quoteValueSymbol . \addcslashes( $value, "\000\n\r\\'\"\032" ) . $this->quoteValueSymbol;
	}
	/**
	 * Quotes an identifier.
	 *
	 * Accepts a string representing a qualified indentifier. For Example:
	 * <code>
	 * $database->quoteIdentifier( 'myschema.mytable' )
	 * </code>
	 * Returns: "myschema"."mytable"
	 *
	 * Or, an array of one or more identifiers that may form a qualified identifier:
	 * <code>
	 * $database->quoteIdentifier( array( 'myschema', 'my.table' ) )
	 * </code>
	 * Returns: "myschema"."my.table"
	 *
	 * The actual quote character surrounding the identifiers may vary depending on
	 * the adapter.
	 *
	 * @param string|array $ident The identifier.
	 * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
	 * @return string The quoted identifier.
	 */
	public final function quoteIdentifier( $ident, $auto = false )	{
		return $this->quoteIdentifierAs( $ident, null, $auto );
	}
	/**
	 * Quote a column identifier and alias.
	 * @param string|array $ident The identifier or expression.
	 * @param string $alias An alias for the column.
	 * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
	 * @return string The quoted identifier and alias.
	 */
	public final function quoteColumnAs( $ident, $alias, $auto = false )	{
		return $this->quoteIdentifierAs( $ident, $alias, $auto );
	}
	/**
	 * Quote a table identifier and alias.
	 *
	 * @param string|array $ident The identifier or expression.
	 * @param string $alias An alias for the table.
	 * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
	 * @return string The quoted identifier and alias.
	 */
	public final function quoteTableAs( $ident, $alias = null, $auto = false )	{
		return $this->quoteIdentifierAs( $ident, $alias, $auto );
	}
	/**
	 * Quote an identifier and an optional alias.
	 *
	 * @param string|array $ident The identifier or expression.
	 * @param string $alias An optional alias.
	 * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
	 * @param string $as The string to add between the identifier/expression and the alias.
	 * @return string The quoted identifier and alias.
	 */
	protected function quoteIdentifierAs( $ident, $alias = null, $auto = false, $as = ' AS ' )	{
		$quoted = "";
		if ($ident instanceof \dayscript\datasource\sql\Select) {
            $quoted = '(' . $ident->assemble() . ')';
        } else {
			if ( \is_string( $ident ) )	{
				$ident = \explode( '.', $ident );
			}
			
			if ( \is_array( $ident ) )	{
				$segments = array( );
				foreach ( $ident as $segment )
					$segments[] = $this->quoteIdentifierWithSymbol( $segment, $auto );
				
				if ( $alias !== null && \end( $ident ) == $alias )
					$alias = null;
				
				$quoted = \implode( '.', $segments );
			}
			else
				$quoted = $this->quoteIdentifierWithSymbol( $ident, $auto );
        }
		if ( $alias !== null )
			$quoted .= $as . $this->quoteIdentifierWithSymbol( $alias, $auto );
		
		return $quoted;
	}
	/**
	 * Quote an identifier.
	 *
	 * @param string $value The identifier or expression.
	 * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
	 * @return string $quoted The quoted identifier and alias.
	 */
	protected final function quoteIdentifierWithSymbol( $value, $auto = false )	{
		if ( $auto === false || $this->autoQuoteIdentifiers === true )	{
			$q = $this->quoteIdentifierSymbol;
			return ( $q . \str_replace( $q, $q . $q, $value ) . $q );
		}
		return $value;
	}
	/**
	 * Returns the symbol the adapter uses for delimited identifiers.
	 * @return string
	 */
	public function getQuoteIdentifierSymbol( )	{
		return $this->quoteIdentifierSymbol;
	}
	/**
	 * Returns the symbol the adapter uses for delimited values.
	 * @return string
	 */
	public function getQuoteValueSymbol( )	{
		return $this->quoteValueSymbol;
	}
	/**
	 * Quotes a value and places into a piece of text at a placeholder.
	 *
	 * The placeholder is a question-mark; all placeholders will be replaced with the quoted value.
	 * For example:
	 *
	 * <code>
	 * $text = "WHERE date < ?";
	 * $date = "2005-01-02";
	 * $safe = $sql->quoteInto($text, $date);
	 * // $safe = "WHERE date < '2005-01-02'"
	 * </code>
	 *
	 * @param string  $text  The text with a placeholder.
	 * @param mixed   $value The value to quote.
	 * @param string  $type  OPTIONAL SQL datatype
	 * @param integer $limit OPTIONAL limit of placeholders to replace
	 * @param &integer $count OPTIONAL count of placeholders already replaced
	 * @return string An SQL-safe quoted value placed into the original text.
	 */
	public final function quoteInto( $text, $value, $type = null, $limit = 1, &$count = null )	{
		return \preg_replace( '/\\?/', $this->quote( $value, $type ), $text, $limit, $count );
		//return \str_replace( '?', $this->quote( $value, $type ), $text, $count );
	}
	 
	/*
	 * Sequences
	 */
	/**
	 * Return the most recent value from the specified sequence in the database.
	 * This is supported only on RDBMS brands that support sequences
	 * (e.g. Oracle, PostgreSQL, DB2).  Other RDBMS brands return null.
	 *
	 * @param string $sequenceName
	 * @return string
	 */
	public function lastSequenceId( $sequenceName )	{
		return null;
	}
	/**
	 * Generate a new value from the specified sequence in the database, and return it.
	 * This is supported only on RDBMS brands that support sequences
	 * (e.g. Oracle, PostgreSQL, DB2).  Other RDBMS brands return null.
	 *
	 * @param string $sequenceName
	 * @return string
	 */
	public function nextSequenceId( $sequenceName )	{
		return null;
	}
	
	/*
	 * Table methods
	 */
	/**
	 * Returns a list of the tables in the database.
	 * @return array
	 */
	abstract public function listTables( );
	/**
	 * Returns the column descriptions for a table.
	 *
	 * The return value is an associative array keyed by the column name,
	 * as returned by the RDBMS.
	 *
	 * The value of each array element is an associative array
	 * with the following keys:
	 *
	 * SCHEMA_NAME		=> string; name of database or schema
	 * TABLE_NAME		=> string;
	 * COLUMN_NAME		=> string; column name
	 * COLUMN_POSITION	=> number; ordinal position of column in table
	 * DATA_TYPE		=> string; SQL datatype name of column
	 * DEFAULT			=> string; default expression of column, null if none
	 * NULLABLE			=> boolean; true if column can have nulls
	 * LENGTH			=> number; length of CHAR/VARCHAR
	 * SCALE			=> number; scale of NUMERIC/DECIMAL
	 * PRECISION		=> number; precision of NUMERIC/DECIMAL
	 * UNSIGNED			=> boolean; unsigned property of an integer type
	 * PRIMARY			=> boolean; true if column is part of the primary key
	 * PRIMARY_POSITION	=> integer; position of column in primary key
	 *
	 * @param string $tableName
	 * @param string $schemaName OPTIONAL
	 * @return array
	 */
	abstract public function describeTable( $tableName, $schemaName = null );

	
	/*
	 * General
	 */
	/**
	 * Convert an array, string into a string to put in a WHERE clause.
	 *
	 * When a vector array is passed, every values will be used as string and all of them will be joined using a ' AND '.
	 * When a non vector array is passed, every key ($cond) will be used as a condition with a placeholder and the value
	 * will be quoted into the $cond and the new values will be used as string and all of them will be joined using a ' AND '.
	 *
	 * @param mixed $where
	 * @return string
	 */
	protected final function whereExpresion( $where )	{
		if( $where )	{
			if ( !\is_array( $where ) )
				$where = array( $where );
			foreach ( $where as $cond => $term )
				$where[$cond] = '( ' . ( !\is_numeric( $cond ) ? $this->quoteInto( $cond, $term ) : $term ) . ' )';
			$where = \implode( ' AND ', $where );
		}
		return $where;
	}
	/**
	 * Inserts a table row with specified data.
	 *
	 * @param mixed $table The table to insert data into.
	 * @param array $bind Column-value pairs.
	 * @return int The number of affected rows.
	 */
	public function insert( $table, array $bind )	{
		// extract and quote col names from the array keys
		$cols = array( );
		$vals = array( );
		foreach ( $bind as $col => $val )	{
			$cols[] = $this->quoteIdentifier( $col, true );
			$vals[] = '?';
		}

		// build the statement
		$sql = 'INSERT INTO ' . $this->quoteIdentifier( $table, true ) .
			' (' . \implode( ', ', $cols ) . ') VALUES (' . \implode( ', ', $vals ) . ')';

		$statement = $this->query( $sql, \array_values( $bind ) );
		return $statement->rowCount( );
	}
	/**
	 * Updates table rows with specified data based on a WHERE clause.
	 *
	 * @param mixed $table The table to update.
	 * @param array $bind Column-value pairs.
	 * @param mixed $where UPDATE WHERE clause(s).
	 * @return int The number of affected rows.
	 */
	public function update( $table, $bind, $where = null )	{
		$set = array( );
		$i = 0;
		foreach ( $bind as $col => $val )	{
			if ( $this->supportsParameters( 'positional' ) )
				$val = '?';
			else if ( $this->supportsParameters( 'named' ) )	{
				unset( $bind[$col] );
				$bind[':'.$col.$i] = $val;
				$val = ':'.$col.$i;
				$i++;
			}
			else
				throw new \Exception( \get_class( $this ) .' doesn\'t support positional or named binding' );
			
			$set[] = $this->quoteIdentifier( $col, true ) . ' = ' . $val;
		}

		$where = $this->whereExpresion( $where );
		$sql = "UPDATE " . $this->quoteIdentifier( $table, true ) . ' SET ' . \implode( ', ', $set ) . ( $where ? ' WHERE ' . $where : '' );
		$statement = $this->query( $sql, ( $this->supportsParameters( 'positional' ) ? \array_values( $bind ) : $bind ) );
		return $statement->rowCount( );
	}
	/**
	 * Deletes table rows based on a WHERE clause.
	 *
	 * @param mixed $table The table to update.
	 * @param mixed $where DELETE WHERE clause(s).
	 * @return int The number of affected rows.
	 */
	public final function delete( $table, $where = null )	{
		$where = $this->whereExpresion( $where );
		$sql = 'DELETE FROM ' . $this->quoteIdentifier( $table, true ) . ( $where ? ' WHERE ' . $where : '' );
		$stmt = $this->query( $sql );
		$result = $stmt->rowCount( );
		return $result;
	}
	/**
	 * Prepares and executes an SQL statement with bound data.
	 *
	 * @param  mixed  $sql  The SQL statement with placeholders.
	 * @param  mixed  $bind An array of data to bind to the placeholders.
	 * @return Zend_Db_Statement_Interface
	 * TODO SELECT Object
	 */
	public final function query( $sql, $bind = array( ) )	{
		$this->openConnection( );

		if ( $sql instanceof \dayscript\datasource\sql\Select )	{
			if ( empty( $bind ) )
				$bind = $sql->getBind( );
			$sql = $sql->assemble( );
		}
		
		if ( !\is_array( $bind ) )
			$bind = array( $bind );

		$statement = $this->prepare( $sql );
		$statement->execute( $bind );
		$statement->setFetchMode( $this->fetchMode );
		return $statement;
	}
	
	/**
	 * Prepare a statement and return a PDOStatement-like object.
	 *
	 * @param string|Zend_Db_Select $sql SQL query
	 * @return Zend_Db_Statement|PDOStatement
	 * TODO
	 */
	abstract public function prepare( $sql );

	/**
	 * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
	 *
	 * As a convention, on RDBMS brands that support sequences (e.g. Oracle, PostgreSQL, DB2), this method forms the
	 * name of a sequence from the arguments and returns the last id generated by that sequence.
	 * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method returns the last value generated
	 * for such a column, and the table name argument is disregarded.
	 *
	 * @param string $tableName OPTIONAL Name of table.
	 * @param string $primaryKey OPTIONAL Name of primary key column.
	 * @return string
	 */
	abstract public function lastInsertId( $tableName = null, $primaryKey = null );
	/**
	 * Set the fetch mode.
	 *
	 * @param integer $mode
	 * @return void
	 */
	abstract public function setFetchMode( $mode );

	/**
	 * Adds an adapter-specific LIMIT clause to the SELECT statement.
	 *
	 * @param mixed $sql
	 * @param integer $count
	 * @param integer $offset
	 * @return string
	 */
	abstract public function limit( $sql, $count, $offset = 0 );
	/**
	 * Check if the adapter supports real SQL parameters.
	 *
	 * @param string $type 'positional' or 'named'
	 * @return bool
	 */
	abstract public function supportsParameters( $type );

	/**
	 * Retrieve server version in PHP style
	 *
	 * @return string
	 */
	abstract public function getServerVersion( );

	/*
	 * Fetch data
	 */
	/**
	 * Fetches all SQL result rows as a sequential array.
	 * Uses the current fetchMode for the adapter.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed $bind Data to bind into SELECT placeholders.
	 * @param mixed $fetchMode Override current fetch mode.
	 * @return array
	 */
	public final function fetchAll( $sql, $bind = array( ), $fetchMode = null )	{
		if ( !$fetchMode )
			$fetchMode = $this->fetchMode;
		
		$statement = $this->query( $sql, $bind );
		return $statement->fetchAll( $fetchMode );
	}
	/**
	 * Fetches the first row of the SQL result.
	 * Uses the current fetchMode for the adapter.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed $bind Data to bind into SELECT placeholders.
	 * @param mixed $fetchMode Override current fetch mode.
	 * @return array
	 */
	public final function fetchRow( $sql, $bind = array( ), $fetchMode = null )	{
		if ( !$fetchMode )
			$fetchMode = $this->fetchMode;
		
		$statement = $this->query( $sql, $bind );
		return $statement->fetch( $fetchMode );
	}
	/**
	 * Fetches all SQL result rows as an associative array.
	 *
	 * The first column is the key, the entire row array is the value. You should construct the query to be sure that the
	 * first column contains unique values, or else rows with duplicate values in the first column will overwrite previous data.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed $bind Data to bind into SELECT placeholders.
	 * @return string
	 */
	public final function fetchAssoc( $sql, $bind = array( ) )	{
		$statement = $this->query( $sql, $bind );
		$data = array( );
		while ( ( $row = $statement->fetch( self::FETCH_ASSOC ) ) != null ) {
			$tmp = \array_values( \array_slice( $row, 0, 1 ) );
			$data[$tmp[0]] = $row;
		}
		return $data;
	}
	/**
	 * Fetches the first column of all SQL result rows as an array.
	 *
	 * The first column in each row is used as the array key.
	 *
	 * @param string $sql An SQL SELECT statement.
	 * @param mixed $bind Data to bind into SELECT placeholders.
	 * @return array
	 */
	public final function fetchCol( $sql, $bind = array( ) )	{
		$statement = $this->query( $sql, $bind );
		$result = $statement->fetchAll( self::FETCH_COLUMN, 0 );
		return $result;
	}
	/**
	 * Fetches all SQL result rows as an array of key-value pairs.
	 *
	 * The first column is the key, the second column is the value.
	 *
	 * @param string|Zend_Db_Select $sql An SQL SELECT statement.
	 * @param mixed $bind Data to bind into SELECT placeholders.
	 * @return array
	 */
	public final function fetchPairs( $sql, $bind = array( ) )	{
		$statement = $this->query( $sql, $bind );
		$data = array( );
		while ( ( $row = $statement->fetch( self::FETCH_NUM ) ) != null )
			$data[$row[0]] = $row[1];
		return $data;
	}
	/**
	 * Fetches the first column of the first row of the SQL result.
	 *
	 * @param string|Zend_Db_Select $sql An SQL SELECT statement.
	 * @param mixed $bind Data to bind into SELECT placeholders.
	 * @return string
	 */
	public final function fetchOne( $sql, $bind = array( ) )	{
		$stmt = $this->query( $sql, $bind );
		$result = $stmt->fetchColumn( 0 );
		return $result;
	}

}
